/*
 * ModelCC, distributed under ModelCC Shared Software License, www.modelcc.org
 */

package org.modelcc.parser.fence;

import java.io.Reader;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.HashMap;

import org.modelcc.IModel;
import org.modelcc.language.syntax.ParserMetadata;
import org.modelcc.language.syntax.Symbol;
import org.modelcc.language.syntax.SyntaxSpecification;
import org.modelcc.lexer.Lexer;
import org.modelcc.parser.Parser;
import org.modelcc.parser.ParserException;

/**
 * ModelCC FenceParser
 * 
 * @param <T> the parser type

 * @author Luis Quesada (lquesada@modelcc.org) & Fernando Berzal (fberzal@modelcc.org)
 */
public class FenceParser<T> extends Parser implements Serializable 
{
    /**
     * The generic parser.
     */
    private Fence gp;

    /**
     * The generic lexer.
     */
    private Lexer gl;

    /**
     * The syntactic specification.
     */
    private SyntaxSpecification ls;

    /**
     * Protected constructor.
     */
    protected FenceParser() 
    {	
    }
    
    /**
     * Constructor
     * @param gl the generic lexer
     * @param gp the generic parser
     * @param ls the language specification
     */
    public FenceParser(Lexer gl,Fence gp,SyntaxSpecification ls) {
        this.gl = gl;
        this.gp = gp;
        this.ls = ls;
    }
    
    
    public ParserMetadata createParserMetadata ()
    {
    	ParserMetadata metadata = new ParserMetadata (ls.getGrammar().getModel());
    	
    	for (IModel element: (List<IModel>)predefined ) {
    		metadata.add(element);
    	}
    	
    	return metadata;
    }

    /**
     * Parses an input string
     * @param input the input string
     * @return a collection of parsed objects
     */
    @Override
     public Collection<T> parseAll(Reader input) throws ParserException 
     {
    	ParserMetadata metadata = createParserMetadata();
    	
        SyntaxGraph sg = gp.parse(metadata,ls,gl.scan(input),objectMetadata);
        Set<T> out = new HashSet<T>();
        for (Iterator<Symbol> ite = sg.getRoots().iterator();ite.hasNext();) {
        	Symbol rootSymbol = ite.next();
            out.add((T)rootSymbol.getUserData());
        }
        if (out.isEmpty()) {
        	throw new ParserException("Syntax error");
        }
        return out;
    }

	/**
     * Parses an input string
     * @param input the input string
     * @return a parsed object
     */
    @Override
    public T parse(String input) throws ParserException {
        Iterator<T> ite = parseIterator(input);
        if (ite.hasNext())
            return ite.next();
        else
            return null;
    }

    /**
     * Parses an input reader
     * @param input the input reader
     * @return a parsed object
     */
    @Override
    public T parse(Reader input) throws ParserException {
        return parseIterator(input).next();
    }

    /**
     * Parses an input string
     * @param input the input string
     * @return an iterator to the collection of parsed objects
     */
    @Override
    public Iterator<T> parseIterator(String input) throws ParserException {
        return parseAll(input).iterator();
    }

    /**
     * Parses an input reader
     * @param input the input reader
     * @return an iterator to the collection of parsed objects
     */
    @Override
    public Iterator<T> parseIterator(Reader input) throws ParserException {
        return parseAll(input).iterator();
    }

    /**
     * Object metadata warehouse.
     */
    private Map<String,Map<Object,Object>> objectMetadata = new HashMap<String,Map<Object,Object>>();
    
    /**
     * Returns the parsing metadata for an object.
     * @param object an object instantiated during the parsing.
     * @return the parsing metadata.
     */
	@Override
	public Map<String,Object> getParsingMetadata(Object object) 
	{
		Map<String,Object> map = new HashMap<String,Object>();
		
		for (String metadata: objectMetadata.keySet()) {
			map.put(metadata, objectMetadata.get(metadata).get(object));
		}
		
		return map;
	}
}
